home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / l-len < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  36.6 KB  |  1,043 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) l-len.gawk 1.2 97/03/26
  3. # 92/08/20 john h. dubois iii (john@armory.com)
  4. # 92/09/26 added help
  5. # 95/04/18 Added 1t options..  Do sorting in awk.
  6. # 95/08/06 Give -L option to l so that ->name won't be printed for symlinks
  7. # 97/03/26 Added lCpx options.  Converted to #!awk program.
  8.  
  9. BEGIN {
  10.     Name = "l-len"
  11.     Usage = "Usage: " Name " [-1htCp] [-l<length>] [<l-options>] [file|dir ...]"
  12.     ARGC = Opts(Name,Usage,"hl>1tCpx",0,"","",0,"",0,"","l,C,f")
  13.     if ("h" in Options) {
  14.     printf \
  15. "%s: list files sorted by filename length, longest first.\n"\
  16. "%s\n"\
  17. "If any option not given here is enountered, it and all further options are\n"\
  18. "passed to l(C).\n"\
  19. "Options:\n"\
  20. "-h: Print this help.\n"\
  21. "-l<length>: List only filenames at least <length> characters long.\n"\
  22. "-C: List only those filenames that would have to be shortened in order for\n"\
  23. "    ls -C to potentially print one more column across.\n"\
  24. "-p: Like -c, but for ls -f or -Cp.\n"\
  25. "-1: Print filenames only.  The default is to print the name length followed\n"\
  26. "    by the l(C)-style output.\n"\
  27. "-t: Print only as many lines and columns as will fit on the screen.\n",
  28.     Name,Usage
  29.     exit 0
  30.     }
  31.     Debug = "x" in Options
  32.     if ("1" in Options) {
  33.     cmd = "ls"
  34.     nPos = 0
  35.     printLen = 0
  36.     }
  37.     else {
  38.     cmd = "l -L"
  39.     nPos = 54
  40.     printLen = 1
  41.     }
  42.     if ("l" in Options)
  43.     minlen = Options["l"]
  44.     else if ("C" in Options)
  45.     fixedWidth = 2
  46.     else if ("p" in Options)
  47.     fixedWidth = 3
  48.     if ((top = "t" in Options) || fixedWidth)
  49.     TopInit()
  50.     if (fixedWidth) {
  51.     printCols = COLUMNS
  52.     moreCols = 1
  53.     }
  54.     if (!top)    # if we only used TopInit() to set COLUMNS, turn it off
  55.     COLUMNS = LINES = 0
  56.     if (printLen) {
  57.     cmd | getline
  58.     TopPrint($0)
  59.     }
  60.     while ((cmd | getline) == 1)
  61.     if ((len = length($0) - nPos) >= minlen) {
  62.         Lens[++i] = len
  63.         if (fixedWidth && len > maxlen)
  64.         maxlen = len
  65.         Lines[i] = $0
  66.     }
  67.     if (fixedWidth) {
  68.     # We want to print moreCols more than the current number of columns.
  69.     cols = int(printCols/(maxlen + fixedWidth))+moreCols
  70.     minlen = int(printCols/cols)-fixedWidth
  71.     if (Debug)
  72.         printf \
  73.         "Currently: %d columns; want: %d columns.  Length: %d->%d.\n",
  74.         cols - moreCols,cols,maxlen,minlen
  75.     if (minlen < 1) {
  76.         print "No more columns can be printed."
  77.         exit 0
  78.     }
  79.     Num = i
  80.     for (; i > 0; i--)
  81.         if (Lens[i] < minlen) {
  82.         delete Lens[i]
  83.         delete Lines[i]
  84.         Num--
  85.         }
  86.     PackArr(Lens,Num,1)
  87.     PackArr(Lines,Num,1)
  88.     }
  89.     num = qsortArbIndByValue(Lens,k)
  90.     for (i = num; i >= 1; i--)
  91.     if (printLen)
  92.         TopPrint(sprintf("%2d ",Lens[k[i]]) Lines[k[i]])
  93.     else
  94.         TopPrint(Lines[k[i]])
  95. }
  96.  
  97. # @(#) top.awk 95/04/18
  98. # Turn on screen-bounded printing.
  99. # Sets global vars LINES and COLUMNS.
  100. # Set either of them to 0 after calling this function if you do not want
  101. # limiting of lines or line length respectively.
  102. function TopInit() {
  103.     # tput will use values in environment, but we want to avoid running
  104.     # it if possible.
  105.     if ("COLUMNS" in ENVIRON)
  106.     COLUMNS = ENVIRON["COLUMNS"]
  107.     else {
  108.     Cmd = "tput cols"
  109.     Cmd | getline COLUMNS
  110.     close(Cmd)
  111.     if (COLUMNS == "")
  112.         COLUMNS = 80
  113.     }
  114.     if ("LINES" in ENVIRON)
  115.     LINES = ENVIRON["LINES"]
  116.     else {
  117.     Cmd = "tput lines"
  118.     Cmd | getline LINES
  119.     close(Cmd)
  120.     if (LINES == "")
  121.         LINES = 24
  122.     }
  123. }
  124.  
  125. # Do screen-bound printing.  
  126. # If LINES is >0, returns 0 when LINES-1 lines have been printed by TopPrint().
  127. # Otherwise returns 1.
  128. # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
  129. # Global vars: uses LINES & COLUMNS; sets/uses LinesPrinted.
  130. function TopPrint(Line) {
  131.     # Check first, in case some calls of this function to not check return
  132.     # value, and in case LINES is 1.
  133.     if (LINES && LinesPrinted >= (LINES-1))
  134.     return 0
  135.     if (COLUMNS)
  136.     print substr(Line,1,COLUMNS - 1)
  137.     else
  138.     print Line
  139.     if (LINES && ++LinesPrinted >= (LINES-1))
  140.     return 0
  141.     return 1
  142. }
  143. ### Begin qsort routines
  144.  
  145. # Arr[] is an array of values with arbitrary indices.
  146. # k[] is returned with numeric indices 1..n.
  147. # The values in k[] are the indices of Arr[],
  148. # ordered so that if Arr[] is stepped through
  149. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  150. # through in order of the values of its elements.
  151. # The return value is the number of elements in the arrays (n).
  152. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  153.     ElNum = 0
  154.     for (ArrInd in Arr)
  155.     k[++ElNum] = ArrInd
  156.     qsortSegment(Arr,k,1,ElNum)
  157.     return ElNum
  158. }
  159.  
  160. # Sort a segment of an array.
  161. # Arr[] contains data with arbitrary indices.
  162. # k[] has indices 1..nelem, with the indices of arr[] as values.
  163. # This function sorts the elements of arr that are pointed to by
  164. # k[start..end], swapping the values of elements of k[] so that
  165. # when this function returns arr[k[start..end]] will be in order.
  166. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  167.     # handle two-element case explicitly for a tiny speedup
  168.     if ((end - start) == 1) {
  169.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  170.         k[start] = tmpe
  171.         k[end] = tmps
  172.     }
  173.     return
  174.     }
  175.     # Make sure comparisons act on these as numbers
  176.     left = start+0
  177.     right = end+0
  178.     sepval = Arr[k[int((left + right) / 2)]]
  179.     # Make every element <= sepval be to the left of every element > sepval
  180.     while (left < right) {
  181.     while (Arr[k[left]] < sepval)
  182.         left++
  183.     while (Arr[k[right]] > sepval)
  184.         right--
  185.     if (left < right) {
  186.         tmp = k[left]
  187.         k[left++] = k[right]
  188.         k[right--] = tmp
  189.     }
  190.     }
  191.     if (left == right)
  192.     if (Arr[k[left]] < sepval)
  193.         left++
  194.     else
  195.         right--
  196.     if (start < right)
  197.     qsortSegment(Arr,k,start,right)
  198.     if (left < end)
  199.     qsortSegment(Arr,k,left,end)
  200. }
  201.  
  202. # Arr[] is an array of values with arbitrary indices.
  203. # k[] is returned with numeric indices 1..n.
  204. # The values in k are the indices of Arr[],
  205. # ordered so that if Arr[] is stepped through
  206. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  207. # through in order of the values of its indices.
  208. # The return value is the number of elements in the arrays (n).
  209. # If the indexes are numeric, Numeric should be true, so that they can be
  210. # compared as such rather than as strings.  Numeric indexes do not have to be
  211. # contiguous.
  212. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  213.     ElNum = 0
  214.     if (Numeric)
  215.     # Indexes do not preserve numeric type, so must be forced
  216.     for (ArrInd in Arr)
  217.         k[++ElNum] = ArrInd+0
  218.     else
  219.     for (ArrInd in Arr)
  220.         k[++ElNum] = ArrInd
  221.     qsortNumIndByValue(k,1,ElNum)
  222.     return ElNum
  223. }
  224.  
  225. # Arr is an array of elements with contiguous numeric indexes to be sorted
  226. # by value.
  227. # start and end are the starting and ending indexes of the range to be sorted.
  228. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  229.     # handle two-element case explicitly for a tiny speedup
  230.     if ((start - end) == 1) {
  231.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  232.         Arr[start] = tmpe
  233.         Arr[end] = tmps
  234.     }
  235.     return
  236.     }
  237.     left = start+0
  238.     right = end+0
  239.     sepval = Arr[int((left + right) / 2)]
  240.     while (left < right) {
  241.     while (Arr[left] < sepval)
  242.         left++
  243.     while (Arr[right] > sepval)
  244.         right--
  245.     if (left <= right) {
  246.         tmp = Arr[left]
  247.         Arr[left++] = Arr[right]
  248.         Arr[right--] = tmp
  249.     }
  250.     }
  251.     if (start < right)
  252.     qsortNumIndByValue(Arr,start,right)
  253.     if (left < end)
  254.     qsortNumIndByValue(Arr,left,end)
  255. }
  256.  
  257. ### End qsort routines
  258. ### Start of ProcArgs library
  259. # @(#) ProcArgs 1.12 97/02/22
  260. # 92/02/29 john h. dubois iii (john@armory.com)
  261. # 93/07/18 Added "#" arg type
  262. # 93/09/26 Do not count -h against MinArgs
  263. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  264. #          Removed meaning of "+" or "-" by itself.
  265. # 94/03/08 Added & option and *()< option types.
  266. # 94/04/02 Added NoRCopt to Opts()
  267. # 94/06/11 Mark numeric variables as such.
  268. # 94/07/08 Opts(): Do not require any args if h option is given.
  269. # 95/01/22 Record options given more than once.  Record option num in argv.
  270. # 95/06/08 Added ExclusiveOptions().
  271. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  272. #          Expand $VARNAME at the start of its filenames.
  273. #          Let varname=0 and -option- turn off an option.
  274. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  275. #          of the vars should be searched for in the environment.
  276. #          Check for duplicate rcfiles.
  277. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  278. #          now return various negatives values on error, not just -1, and
  279. #          Opts() may set Err to various positive values, not just 1.
  280. #          Added AllowUnrecOpt.
  281. # 96/05/23 Check type given for & option
  282. # 96/06/15 Re-port to awk
  283. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  284. #          used by other functions.
  285. # 96/10/15 Added OptChars
  286. # 96/11/01 Added exOpts arg to Opts()
  287. # 96/11/16 Added ; type
  288. # 96/12/08 Added Opt2Set() & Opt2Sets()
  289. # 96/12/27 Added CmdLineOpt()
  290. # 97/02/22 Remove packed elements.
  291. # 97/02/28 Make sequence # for rcfiles & environ be "f" and "e".
  292. #          Replaced CmdLineOpt() with OptsGiven().
  293.  
  294. # optlist is a string which contains all of the possible command line options.
  295. # A character followed by certain characters indicates that the option takes
  296. # an argument, with type as follows:
  297. # :    String argument
  298. # ;    Non-empty string argument
  299. # *    Floating point argument
  300. # (    Non-negative floating point argument
  301. # )    Positive floating point argument
  302. # #    Integer argument
  303. # <    Non-negative integer argument
  304. # >    Positive integer argument
  305. # The only difference the type of argument makes is in the runtime argument
  306. # error checking that is done.
  307.  
  308. # The & option is a special case used to get numeric options without the
  309. # user having to give an option character.  It is shorthand for [-+.0-9].
  310. # If & is included in optlist and an option string that begins with one of
  311. # these characters is seen, the value given to "&" will include the first
  312. # char of the option.  & must be followed by a type character other than ":"
  313. # or ";".
  314. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  315.  
  316. # Strings in argv[] which begin with "-" or "+" are taken to be
  317. # strings of options, except that a string which consists solely of "-"
  318. # or "+" is taken to be a non-option string; like other non-option strings,
  319. # it stops the scanning of argv and is left in argv[].
  320. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  321. # If an option takes an argument, the argument may either immediately
  322. # follow it or be given separately.
  323. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  324. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  325. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  326. # this feature had a flaw that caused problems in some cases.  See the OptChars
  327. # parameter to explicitly set the option-specifier characters.
  328.  
  329. # If an option that does not take an argument is given,
  330. # an index with its name is created in Options and its value is set to the
  331. # number of times it occurs in argv[].
  332.  
  333. # If an option that does take an argument is given, an index with its name is
  334. # created in Options and its value is set to the value of the argument given
  335. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  336. # If an option that takes an argument is given more than once,
  337. # Options[option-name,"count"] is incremented, and the value is assigned to
  338. # the index (option-name,instance) where instance is 2 for the second occurance
  339. # of the option, etc.
  340. # In other words, the first time an option with a value is encountered, the
  341. # value is assigned to an index consisting only of its name; for any further
  342. # occurances of the option, the value index has an extra (count) dimension.
  343.  
  344. # The sequence number for each option found in argv[] is stored in
  345. # Options[option-name,"num",instance], where instance is 1 for the first
  346. # occurance of the option, etc.  The sequence number starts at 1 and is
  347. # incremented for each option, both those that have a value and those that
  348. # do not.  Options set from a config file get a sequence number of "f", and
  349. # options set in the environment get a sequence number of "e".
  350.  
  351. # Options and their arguments are deleted from argv.
  352. # Note that this means that there may be gaps left in the indices of argv[].
  353. # If compress is nonzero, argv[] is packed by moving its elements so that
  354. # they have contiguous integer indices starting with 0.
  355. # Option processing will stop with the first unrecognized option, just as
  356. # though -- was given except that unlike -- the unrecognized option will not be
  357. # removed from ARGV[].  Normally, an error value is returned in this case.
  358. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  359. # be found, so the number of remaining arguments is returned instead.
  360. # If OptChars is not a null string, it is the set of characters that indicate
  361. # that an argument is an option string if the string begins with one of the
  362. # characters.  A string consisting solely of two of the same option-indicator
  363. # characters stops the scanning of argv[].  The default is "-+".
  364. # argv[0] is not examined.
  365. # The number of arguments left in argc is returned.
  366. # If an error occurs, the global string OptErr is set to an error message
  367. # and a negative value is returned.
  368. # Current error values:
  369. # -1: option that required an argument did not get it.
  370. # -2: argument of incorrect type supplied for an option.
  371. # -3: unrecognized (invalid) option.
  372. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  373. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  374. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  375. {
  376. # ArgNum is the index of the argument being processed.
  377. # ArgsLeft is the number of arguments left in argv.
  378. # Arg is the argument being processed.
  379. # ArgLen is the length of the argument being processed.
  380. # ArgInd is the position of the character in Arg being processed.
  381. # Option is the character in Arg being processed.
  382. # Pos is the position in OptList of the option being processed.
  383. # NumOpt is true if a numeric option may be given.
  384.     ArgsLeft = argc
  385.     NumOpt = index(OptList,"&")
  386.     OptionNum = 0
  387.     if (OptChars == "")
  388.     OptChars = "-+"
  389.     while (OptChars != "") {
  390.     c = substr(OptChars,1,1)
  391.     OptChars = substr(OptChars,2)
  392.     OptCharSet[c]
  393.     OptTerm[c c]
  394.     }
  395.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  396.     Arg = argv[ArgNum]
  397.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  398.         break    # Not an option; quit
  399.     if (Arg in OptTerm) {
  400.         delete argv[ArgNum]
  401.         ArgsLeft--
  402.         break
  403.     }
  404.     ArgLen = length(Arg)
  405.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  406.         Option = substr(Arg,ArgInd,1)
  407.         if (NumOpt && Option ~ /[-+.0-9]/) {
  408.         # If this option is a numeric option, make its flag be & and
  409.         # its option string flag position be the position of & in
  410.         # the option string.
  411.         Option = "&"
  412.         Pos = NumOpt
  413.         # Prefix Arg with a char so that ArgInd will point to the
  414.         # first char of the numeric option.
  415.         Arg = "&" Arg
  416.         ArgLen++
  417.         }
  418.         # Find position of flag in option string, to get its type (if any).
  419.         # Disallow & as literal flag.
  420.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  421.         if (AllowUnrecOpt) {
  422.             Escape = 1
  423.             break
  424.         }
  425.         else {
  426.             OptErr = "Invalid option: " specGiven Option
  427.             return -3
  428.         }
  429.         }
  430.  
  431.         # Find what the value of the option will be if it takes one.
  432.         # NeedNextOpt is true if the option specifier is the last char of
  433.         # this arg, which means that if the option requires a value it is
  434.         # the next arg.
  435.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  436.         if (GotValue = ArgNum + 1 < argc)
  437.             Value = argv[ArgNum+1]
  438.         }
  439.         else {    # Value is included with option
  440.         Value = substr(Arg,ArgInd + 1)
  441.         GotValue = 1
  442.         }
  443.  
  444.         if (HadValue = AssignVal(Option,Value,Options,
  445.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  446.         specGiven)) {
  447.         if (HadValue < 0)    # error occured
  448.             return HadValue
  449.         if (HadValue == 2)
  450.             ArgInd++    # Account for the single-char value we used.
  451.         else {
  452.             if (NeedNextOpt) {    # option took next arg as value
  453.             delete argv[++ArgNum]
  454.             ArgsLeft--
  455.             }
  456.             break    # This option has been used up
  457.         }
  458.         }
  459.     }
  460.     if (Escape)
  461.         break
  462.     # Do not delete arg until after processing of it, so that if it is not
  463.     # recognized it can be left in ARGV[].
  464.     delete argv[ArgNum]
  465.     ArgsLeft--
  466.     }
  467.     if (compress != 0) {
  468.     dest = 1
  469.     src = argc - ArgsLeft + 1
  470.     if (src != dest) {
  471.         for (count = ArgsLeft - 1; count; count--) {
  472.         ARGV[dest] = ARGV[src]
  473.         dest++
  474.         src++
  475.         }
  476.         for (; dest < src; dest++)
  477.         delete ARGV[dest]
  478.     }
  479.     }
  480.     return ArgsLeft
  481. }
  482.  
  483. # Assignment to values in Options[] occurs only in this function.
  484. # Option: Option specifier character.
  485. # Value: Value to be assigned to option, if it takes a value.
  486. # Options[]: Options array to return values in.
  487. # ArgType: Argument type specifier character.
  488. # GotValue: Whether any value is available to be assigned to this option.
  489. # Name: Name of option being processed.
  490. # OptionNum: Number of this option (starting with 1) if set in argv[],
  491. #     or 0 if it was given in a config file or in the environment.
  492. # SingleOpt: true if the value (if any) that is available for this option was
  493. #     given as part of the same command line arg as the option.  Used only for
  494. #     options from the command line.
  495. # specGiven is the option specifier character use, if any (e.g. - or +),
  496. # for use in error messages.
  497. # Global variables: OptErr
  498. # Return value: negative value on error, 0 if option did not require an
  499. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  500. # the arg.
  501. # Current error values:
  502. # -1: Option that required an argument did not get it.
  503. # -2: Value of incorrect type supplied for option.
  504. # -3: Bad type given for option &
  505. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  506. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  507.     # If option takes a value...    [
  508.     NumTypes = "*()#<>]"
  509.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  510.     OptErr = "Bad type given for & option"
  511.     return -3
  512.     }
  513.  
  514.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  515.     if (!GotValue) {
  516.         if (Name != "")
  517.         OptErr = "Variable requires a value -- " Name
  518.         else
  519.         OptErr = "option requires an argument -- " Option
  520.         return -1
  521.     }
  522.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  523.         OptErr = Err
  524.         return -2
  525.     }
  526.     # Mark this as a numeric variable; will be propogated to Options[] val.
  527.     if (ArgType != ":" && ArgType != ";")
  528.         Value += 0
  529.     if ((Instance = ++Options[Option,"count"]) > 1)
  530.         Options[Option,Instance] = Value
  531.     else
  532.         Options[Option] = Value
  533.     }
  534.     # If this is an environ or rcfile assignment & it was given a value...
  535.     else if (!OptionNum && Value != "") {
  536.     UsedValue = 1
  537.     # If the value is "0" or "-" and this is the first instance of it,
  538.     # do not set Options[Option]; this allows an assignment in an rcfile to
  539.     # turn off an option (for the simple "Option in Options" test) in such
  540.     # a way that it cannot be turned on in a later file.
  541.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  542.         Instance = 1
  543.     else
  544.         Instance = ++Options[Option]
  545.     # Save the value even though this is a flag
  546.     Options[Option,Instance] = Value
  547.     }
  548.     # If this is a command line flag and has a - following it in the same arg,
  549.     # it is being turned off.
  550.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  551.     UsedValue = 2
  552.     if (Option in Options)
  553.         Instance = ++Options[Option]
  554.     else
  555.         Instance = 1
  556.     Options[Option,Instance]
  557.     }
  558.     # If this is a flag assignment without a value, increment the count for the
  559.     # flag unless it was turned off.  The indicator for a flag being turned off
  560.     # is that the flag index has not been set in Options[] but it has an
  561.     # instance count.
  562.     else if (Option in Options || !((Option,1) in Options))
  563.     # Increment number of times this flag seen; will inc null value to 1
  564.     Instance = ++Options[Option]
  565.     Options[Option,"num",Instance] = OptionNum
  566.     return UsedValue
  567. }
  568.  
  569. # Option is the option letter
  570. # Value is the value being assigned
  571. # Name is the var name of the option, if any
  572. # ArgType is one of:
  573. # :    String argument
  574. # ;    Non-null string argument
  575. # *    Floating point argument
  576. # (    Non-negative floating point argument
  577. # )    Positive floating point argument
  578. # #    Integer argument
  579. # <    Non-negative integer argument
  580. # >    Positive integer argument
  581. # specGiven is the option specifier character use, if any (e.g. - or +),
  582. # for use in error messages.
  583. # Returns null on success, err string on error
  584. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  585.     if (ArgType == ":")
  586.     return ""
  587.     if (ArgType == ";") {
  588.     if (Value == "")
  589.         Err = "must be a non-empty string"
  590.     }
  591.     # A number begins with optional + or -, and is followed by a string of
  592.     # digits or a decimal with digits before it, after it, or both
  593.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  594.     Err = "must be a number"
  595.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  596.     Err = "may not include a fraction"
  597.     else if (ArgType ~ "[()<>]" && Value < 0)
  598.     Err = "may not be negative"
  599.     # (
  600.     else if (ArgType ~ "[)>]" && Value == 0)
  601.     Err = "must be a positive number"
  602.     if (Err != "") {
  603.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  604.     if (Name != "")
  605.         return ErrStr "variable " substr(Name,1,1) " " Err
  606.     else {
  607.         if (Option == "&")
  608.         Option = Value
  609.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  610.     }
  611.     }
  612.     else
  613.     return ""
  614. }
  615.  
  616. # Note: only the above functions are needed by ProcArgs.
  617. # The rest of these functions call ProcArgs() and also do other
  618. # option-processing stuff.
  619.  
  620. # Opts: Process command line arguments.
  621. # Opts processes command line arguments using ProcArgs()
  622. # and checks for errors.  If an error occurs, a message is printed
  623. # and the program is exited.
  624. #
  625. # Input variables:
  626. # Name is the name of the program, for error messages.
  627. # Usage is a usage message, for error messages.
  628. # OptList the option description string, as used by ProcArgs().
  629. # MinArgs is the minimum number of non-option arguments that this
  630. # program should have, non including ARGV[0] and +h.
  631. # If the program does not require any non-option arguments,
  632. # MinArgs should be omitted or given as 0.
  633. # rcFiles, if given, is a colon-seprated list of filenames to read for
  634. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  635. # by the value of the environment variable HOME.  If a filename begins with
  636. # $, the part from the character after the $ up until (but not including)
  637. # the first character not in [a-zA-Z0-9_] will be searched for in the
  638. # environment; if found its value will be substituted, if not the filename will
  639. # be discarded.
  640. # rcfiles are read in the order given.
  641. # Values given in them will not override values given on the command line,
  642. # and values given in later files will not override those set in earlier
  643. # files, because AssignVal() will store each with a different instance index.
  644. # The first instance of each variable, either on the command line or in an
  645. # rcfile, will be stored with no instance index, and this is the value
  646. # normally used by programs that call this function.
  647. # VarNames is a comma-separated list of variable names to map to options,
  648. # in the same order as the options are given in OptList.
  649. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  650. # searched for in the environment.  If set to -1, all values will be searched
  651. # for in the environment.  Values given in the environment will override
  652. # those given in the rcfiles but not those given on the command line.
  653. # NoRCopt, if given, is an additional letter option that if given on the
  654. # command line prevents the rcfiles from being read.
  655. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  656. # ExclusiveOptions() for a description of exOpts.
  657. # Special options:
  658. # If x is made an option and is given, some debugging info is output.
  659. # h is assumed to be the help option.
  660.  
  661. # Global variables:
  662. # The command line arguments are taken from ARGV[].
  663. # The arguments that are option specifiers and values are removed from
  664. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  665. # The number of elements in ARGV[] should be in ARGC.
  666. # After processing, ARGC is set to the number of elements left in ARGV[].
  667. # The option values are put in Options[].
  668. # On error, Err is set to a positive integer value so it can be checked for in
  669. # an END block.
  670. # Return value: The number of elements left in ARGV is returned.
  671. # Must keep OptErr global since it may be set by InitOpts().
  672. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  673. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  674.     if (MinArgs == "")
  675.     MinArgs = 0
  676.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  677.     optChars)
  678.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  679.     if (ArgsLeft >= 0) {
  680.         OptErr = "Not enough arguments"
  681.         Err = 4
  682.     }
  683.     else
  684.         Err = -ArgsLeft
  685.     printf "%s: %s.\nUse -h for help.\n%s\n",
  686.     Name,OptErr,Usage > "/dev/stderr"
  687.     exit 1
  688.     }
  689.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  690.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  691.     {
  692.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  693.     Err = -e
  694.     exit 1
  695.     }
  696.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  697.     {
  698.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  699.     Err = 1
  700.     exit 1
  701.     }
  702.     return ArgsLeft
  703. }
  704.  
  705. # ReadConfFile(): Read a file containing var/value assignments, in the form
  706. # <variable-name><assignment-char><value>.
  707. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  708. # line and whitespace between the variable name and the assignment character) 
  709. # is stripped.  Lines that do not contain an assignment operator or which
  710. # contain a null variable name are ignored, other than possibly being noted in
  711. # the return value.  If more than one assignment is made to a variable, the
  712. # first assignment is used.
  713. # Input variables:
  714. # File is the file to read.
  715. # Comment is the line-comment character.  If it is found as the first non-
  716. #     whitespace character on a line, the line is ignored.
  717. # Assign is the assignment string.  The first instance of Assign on a line
  718. #     separates the variable name from its value.
  719. # If StripWhite is true, whitespace around the value (whitespace between the
  720. #     assignment char and trailing whitespace on the line) is stripped.
  721. # VarPat is a pattern that variable names must match.  
  722. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  723. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  724. #     a line; no assignment operator is needed.  These variables are set in
  725. #     the output array with a null value.  Lines containing nothing but
  726. #     whitespace are still ignored.
  727. # Output variables:
  728. # Values[] contains the assignments, with the indexes being the variable names
  729. #     and the values being the assigned values.
  730. # Lines[] contains the line number that each variable occured on.  A flag set
  731. #     is record by giving it an index in Lines[] but not in Values[].
  732. # Return value:
  733. # If any errors occur, a string consisting of descriptions of the errors
  734. # separated by newlines is returned.  In no case will the string start with a
  735. # numeric value.  If no errors occur,  the number of lines read is returned.
  736. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  737. FlagsOK,
  738. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  739.     if (Comment != "")
  740.     Comment = "^" Comment
  741.     AssignLen = length(Assign)
  742.     if (VarPat == "")
  743.     VarPat = "."    # null varname not allowed
  744.     while ((Status = (getline Line < File)) == 1) {
  745.     LineNum++
  746.     sub("^[ \t]+","",Line)
  747.     if (Line == "")        # blank line
  748.         continue
  749.     if (Comment != "" && Line ~ Comment)
  750.         continue
  751.     if (Pos = index(Line,Assign)) {
  752.         Var = substr(Line,1,Pos-1)
  753.         Val = substr(Line,Pos+AssignLen)
  754.         if (StripWhite) {
  755.         sub("^[ \t]+","",Val)
  756.         sub("[ \t]+$","",Val)
  757.         }
  758.     }
  759.     else {
  760.         Var = Line    # If no value, var is entire line
  761.         Val = ""
  762.     }
  763.     if (!FlagsOK && Val == "") {
  764.         Errs = Errs \
  765.         sprintf("\nBad assignment on line %d of file %s: %s",
  766.         LineNum,File,Line)
  767.         continue
  768.     }
  769.     sub("[ \t]+$","",Var)
  770.     if (Var !~ VarPat) {
  771.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  772.         LineNum,File,Var)
  773.         continue
  774.     }
  775.     if (!(Var in Lines)) {
  776.         Lines[Var] = LineNum
  777.         if (Pos)
  778.         Values[Var] = Val
  779.     }
  780.     }
  781.     if (Status)
  782.     Errs = Errs "\nCould not read file " File
  783.     close(File)
  784.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  785. }
  786.  
  787. # Variables:
  788. # Data is stored in Options[].
  789. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  790. # Global vars:
  791. # Sets OptErr.  Uses ENVIRON[].
  792. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  793. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  794. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  795. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  796.     split("",filesRead,"")    # make awk know this is an array
  797.     NumVars = split(VarNames,Vars,",")
  798.     TypesInd = Ret = 0
  799.     if (EnvSearch == -1)
  800.     EnvSearch = NumVars
  801.     for (i = 1; i <= NumVars; i++) {
  802.     Var = Vars[i]
  803.     CharOpt = substr(OptList,++TypesInd,1)
  804.     if (CharOpt ~ "^[:;*()#<>&]$")
  805.         CharOpt = substr(OptList,++TypesInd,1)
  806.     Map[Var] = CharOpt
  807.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  808.     # Do not overwrite entries from environment
  809.     if (i <= EnvSearch && Var in ENVIRON &&
  810.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,"e")) < 0)
  811.         return Err
  812.     }
  813.  
  814.     numrcFiles = split(rcFiles,fNames,":")
  815.     for (i = 1; i <= numrcFiles; i++) {
  816.     rcFile = fNames[i]
  817.     if (rcFile ~ "^~/")
  818.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  819.     else if (rcFile ~ /^\$/) {
  820.         rcFile = substr(rcFile,2)
  821.         match(rcFile,"^[a-zA-Z0-9_]*")
  822.         envvar = substr(rcFile,1,RLENGTH)
  823.         if (envvar in ENVIRON)
  824.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  825.         else
  826.         continue
  827.     }
  828.     if (rcFile in filesRead)
  829.         continue
  830.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  831.     # may be the same
  832.     filesRead[rcFile]
  833.     if ("x" in Options)
  834.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  835.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  836.     if (retStr > 0)
  837.         READ_RCFILE = 1
  838.     else if (ret != "") {
  839.         OptErr = retStr
  840.         Ret = -1
  841.     }
  842.     for (Var in Lines)
  843.         if (Var in Map) {
  844.         if ((Err = AssignVal(Map[Var],Var in Values ? Values[Var] : "",
  845.         Options,Types[Var],Var in Values,Var,"f")) < 0)
  846.             return Err
  847.         }
  848.         else {
  849.         OptErr = sprintf(\
  850.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  851.         Lines[Var],rcFile)
  852.         Ret = -1
  853.         }
  854.     }
  855.  
  856.     if ("x" in Options)
  857.     for (Var in Map)
  858.         if (Map[Var] in Options)
  859.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  860.         "/dev/stderr"
  861.         else
  862.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  863.     return Ret
  864. }
  865.  
  866. # OptSets is a semicolon-separated list of sets of option sets.
  867. # Within a list of option sets, the option sets are separated by commas.  For
  868. # each set of sets, if any option in one of the sets is in Options[] AND any
  869. # option in one of the other sets is in Options[], an error string is returned.
  870. # If no conflicts are found, nothing is returned.
  871. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  872. # the exclusions presented by the first set of sets (ab,def,g) if:
  873. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  874. # (a or b is in Options[]) AND (g is in Options) OR
  875. # (d, e, or f is in Options[]) AND (g is in Options)
  876. # An error will be returned due to the exclusions presented by the second set
  877. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  878. # todo: make options given on command line unset options given in config file
  879. # todo: that they conflict with.
  880. function ExclusiveOptions(OptSets,Options,
  881. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  882. SetNum,OSetNum) {
  883.     NumSetSets = split(OptSets,SetSets,";")
  884.     # For each set of sets...
  885.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  886.     # NumSets is the number of sets in this set of sets.
  887.     NumSets = split(SetSets[SetSet],Sets,",")
  888.     # For each set in a set of sets except the last...
  889.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  890.         s1 = Sets[SetNum]
  891.         L1 = length(s1)
  892.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  893.         # If any of the options in this set was given, check whether
  894.         # any of the options in the other sets was given.  Only check
  895.         # later sets since earlier sets will have already been checked
  896.         # against this set.
  897.         if ((c1 = substr(s1,Pos1,1)) in Options)
  898.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  899.             s2 = Sets[OSetNum]
  900.             L2 = length(s2)
  901.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  902.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  903.                 ErrStr = ErrStr "\n"\
  904.                 sprintf("Cannot give both %s and %s options.",
  905.                 c1,c2)
  906.             }
  907.     }
  908.     }
  909.     if (ErrStr != "")
  910.     return substr(ErrStr,2)
  911.     return ""
  912. }
  913.  
  914. # The value of each instance of option Opt that occurs in Options[] is made an
  915. # index of Set[].
  916. # The return value is the number of instances of Opt in Options.
  917. function Opt2Set(Options,Opt,Set,  count) {
  918.     if (!(Opt in Options))
  919.     return 0
  920.     Set[Options[Opt]]
  921.     count = Options[Opt,"count"]
  922.     for (; count > 1; count--)
  923.     Set[Options[Opt,count]]
  924.     return count
  925. }
  926.  
  927. # The value of each instance of option Opt that occurs in Options[] that
  928. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  929. # Other values are made indexes of Set[].
  930. # The return value is the number of instances of Opt in Options.
  931. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  932.     ret = Opt2Set(Options,Opt,aSet)
  933.     for (value in aSet)
  934.     if (substr(value,1,1) == "!")
  935.         nSet[substr(value,2)]
  936.     else
  937.         Set[value]
  938.     return ret
  939. }
  940.  
  941. # Returns true if any option in the string Opts was given, as indicated by the
  942. # data in Options[]. If any of Arg, Env, or File are true, the given opts are
  943. # only considered to have been set if they were set in the command line
  944. # arguments, environment, or in a configuration file, respectively. 
  945. function OptsGiven(Options,Opts,Arg,Env,File,  l,i,Opt,j,c) {
  946.     if (!Arg && !Env && !File)
  947.     Arg = Env = File = 1
  948.     l = length(Opts)
  949.     for (i = 1; i <= l; i++) {
  950.     Opt = substr(Opts,i,1)
  951.     for (j = 1; (Opt,"num",j) in Options; j++) {
  952.         c = Options[Opt,"num",j]
  953.         if (Arg && c+0 > 0 || File && c == "f" || Env && c == "e")
  954.         return 1
  955.     }
  956.     }
  957.     return 0
  958. }
  959. ### End of ProcArgs library
  960. ### Begin array routines
  961. # 97/01/26 Added Order parameter to Assign()
  962.  
  963. # InitArr: Initialize an array with values.
  964. # Ind and Vals are separated into lists on Sep.
  965. # For each item in Ind, an index with that name is created in Arr[],
  966. # and the value with the same position in Vals is stored in it.
  967. # Global variables: none.
  968. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  969.     split(Ind,indnames,sep)
  970.     split(Vals,values,sep)
  971.     for (numind in indnames)
  972.     Arr[indnames[numind]] = values[numind]
  973. }
  974.  
  975. function ClearArr(Arr,  Elem) {
  976.     for (Elem in Arr)
  977.     delete Arr[Elem]
  978. }
  979.  
  980. function CopyArr(From,To,  Elem) {
  981.     for (Elem in From)
  982.     To[Elem] = From[Elem]
  983. }
  984.  
  985. # Subtract the values in Subtrahend from those in Minuend
  986. function SubtractArr(Minuend,Subtrahend,  Elem) {
  987.     for (Elem in Subtrahend)
  988.     Minuend[Elem] -= Subtrahend[Elem]
  989. }
  990. # For each element of the array In, an element is created in Out having
  991. # an index equal to the value of the element in In and a value equal to
  992. # the index of the element in In.
  993. function Invert(In,Out,  Index) {
  994.     for (Index in In)
  995.     Out[In[Index]] = Index
  996. }
  997.  
  998. # Assign: make an array from a list of assignments.
  999. # An index with the name of each variable in the list is created in the array.
  1000. # Its value is set to the value given for it.
  1001. # Input variables:
  1002. # Elements is a string containing the list of variable-value pairs.
  1003. # Sep is the string that separates the pairs in the list.
  1004. # AssignOp is the string that separates variables from values.
  1005. # Output variables:
  1006. # Arr is the array.
  1007. # Order[1..n] are set to the variable names in the order they are given.
  1008. # Return value: the number of elements added to the set, or -1 on error.
  1009. # Example:
  1010. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  1011. function Assign(Arr,Elements,Sep,AssignOp,Order,
  1012. Num,Names,Elem,Assignments,Assignment,i,Var) {
  1013.     Num = split(Elements,Assignments,Sep)
  1014.     for (i = 1; i <= Num; i++) {
  1015.     Assignment = Assignments[i]
  1016.     if (!(Ind = index(Assignment,AssignOp)))
  1017.         return -1
  1018.     Var = substr(Assignment,1,Ind - 1)
  1019.     Arr[Var] = substr(Assignment,Ind + 1)
  1020.     Order[i] = Var
  1021.     }
  1022.     return Num
  1023. }
  1024.  
  1025. # Packs Arr[], which should have integer indices starting at or above n, to
  1026. # contiguous integer indices starting with n.
  1027. # If n is not given it defaults to 0.
  1028. # Num should be the number of elements in Arr.
  1029. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  1030.     NewInd = OldInd = n+0
  1031.     for (; Num; Num--) {
  1032.     while (!(OldInd in Arr))
  1033.         OldInd++
  1034.     if (NewInd != OldInd) {
  1035.         Arr[NewInd] = Arr[OldInd]
  1036.         delete Arr[OldInd]
  1037.     }
  1038.     OldInd++
  1039.     NewInd++
  1040.     }
  1041. }
  1042. ### End array routines
  1043.